#!/usr/bin/env python

import wx
import Image
import ImageDraw
import ImageChops

class RoundedImage(object):
    '''RoundedImage class
    
    @author Julot <andy.julot@gmail.com>
    @since Tuesday, February 07, 2012. 07:47 PM'''
    
    CROSS = 'Cross'
    ROUNDED = 'Rounded'
    SQUARE = 'Square'
    
    ROUNDED_POS = (ROUNDED, ROUNDED, ROUNDED, ROUNDED)
    ROUNDED_RECTANGLE_ID = 'rounded_rectangle_r%d_f%d_s%s_p%s'
    
    _radius = 0
    _pil = None
    
    def __init__(self, image, radius):
        '''@param wx.Image image to rounded
        @param Integer round radius in pixel'''
        
        self._radius = int(radius)
        pil = self._imageToPil(image)
        self._pil = self._roundImage(pil, self._radius)
    
    def convertToImage(self, alpha=True):
        '''@return wx.Image'''
        
        if alpha:
            image = apply(wx.EmptyImage, self._pil.size)
            image.SetData(self._pil.convert("RGB").tostring())
            #image.SetAlphaData(pil.convert("RGBA").tostring()[3::4])
        else:
            image = wx.EmptyImage(pil.size[0], self._pil.size[1])
            new_image = self._pil.convert('RGB')
            data = new_image.tostring()
            image.SetData(data)
        return image
    
    def convertToBitmap(self):
        '''@return wx.Bitmap'''
        
        return wx.BitmapFromImage(self.convertToImage())
        
    def _roundImage(self, image, radius):
        if image.mode != 'RGBA': image = image.convert('RGBA')
        mask = self._roundRectangle(image.size, radius)
    
        self._paste(image, Image.new('RGB', image.size, '#FFFFFF'), (0, 0),
              ImageChops.invert(mask))
        image.putalpha(mask)
        return image
    
    def _paste(self, destination, source, box=(0, 0), mask=None, force=False):
        """"Pastes the source image into the destination image while using an
        alpha channel if available.
    
        :param destination: destination image
        :type destination:  PIL image object
        :param source: source image
        :type source: PIL image object
        :param box:
    
            The box argument is either a 2-tuple giving the upper left corner,
            a 4-tuple defining the left, upper, right, and lower pixel
            coordinate, or None (same as (0, 0)). If a 4-tuple is given, the
            size of the pasted image must match the size of the region.
    
        :type box: tuple
        :param mask: mask or None
    
        :type mask: bool or PIL image object
        :param force:
    
            With mask: Force the invert alpha paste or not.
    
            Without mask:
    
            - If ``True`` it will overwrite the alpha channel of the destination
              with the alpha channel of the source image. So in that case the
              pixels of the destination layer will be abandonned and replaced
              by exactly the same pictures of the destination image. This is
              mostly what you need if you paste on a transparant canvas.
            - If ``False`` this will use a mask when the image has an alpha
              channel. In this case pixels of the destination image will appear
              through where the source image is transparent.
    
        :type force: bool
        """
        # Paste on top
        if source == mask:
            if has_alpha(source):
                # invert_alpha = the transparant pixels of the destination
                if has_alpha(destination) and (destination.size == source.size
                        or force):
                    invert_alpha = ImageOps.invert(get_alpha(destination))
                    if invert_alpha.size != source.size:
                        # if sizes are not the same be careful!
                        # check the results visually
                        if len(box) == 2:
                            w, h = source.size
                            box = (box[0], box[1], box[0] + w, box[1] + h)
                        invert_alpha = invert_alpha.crop(box)
                else:
                    invert_alpha = None
                # we don't want composite of the two alpha channels
                source_without_alpha = remove_alpha(source)
                # paste on top of the opaque destination pixels
                destination.paste(source_without_alpha, box, source)
                if invert_alpha != None:
                    # the alpha channel is ok now, so save it
                    destination_alpha = get_alpha(destination)
                    # paste on top of the transparant destination pixels
                    # the transparant pixels of the destination should
                    # be filled with the color information from the source
                    destination.paste(source_without_alpha, box, invert_alpha)
                    # restore the correct alpha channel
                    destination.putalpha(destination_alpha)
            else:
                destination.paste(source, box)
        elif mask:
            destination.paste(source, box, mask)
        else:
            destination.paste(source, box)
            if force and has_alpha(source):
                destination_alpha = get_alpha(destination)
                source_alpha = get_alpha(source)
                destination_alpha.paste(source_alpha, box)
                destination.putalpha(destination_alpha)
    
    def _roundRectangle(self, size, radius):
        opacity = 255
        pos = self.ROUNDED_POS
        #rounded_rectangle
        im_x, im_y = size
        #cross
        cross = Image.new('L', size, 0)
        draw = ImageDraw.Draw(cross)
        draw.rectangle((radius, 0, im_x - radius, im_y), fill=opacity)
        draw.rectangle((0, radius, im_x, im_y - radius), fill=opacity)
        #corner
        corner = self._corner(radius, opacity)
        #rounded rectangle
        rectangle = Image.new('L', (radius, radius), 255)
        rounded_rectangle = cross.copy()
        for index, angle in enumerate(pos):
            if angle == self.CROSS:
                continue
            if angle == self.ROUNDED:
                element = corner
            else:
                element = rectangle
            if index % 2:
                x = im_x - radius
                element = element.transpose(Image.FLIP_LEFT_RIGHT)
            else:
                x = 0
            if index < 2:
                y = 0
            else:
                y = im_y - radius
                element = element.transpose(Image.FLIP_TOP_BOTTOM)
            self._paste(rounded_rectangle, element, (x, y))
        return rounded_rectangle
    
    def _corner(self, radius=100, opacity=255, factor=2):
        corner = Image.new('L', (factor * radius, factor * radius), 0)
        draw = ImageDraw.Draw(corner)
        draw.pieslice((0, 0, 2 * factor * radius, 2 * factor * radius),
                      180, 270, fill=opacity)
        corner = corner.resize((radius, radius), Image.ANTIALIAS)
        return corner
    
    def _imageToPil(self, image):
        '''Convert wx.Image to PIL Image.'''
        
        w, h = image.GetSize()
        data = image.GetData()
        
        redImage = Image.new("L", (w, h))
        redImage.fromstring(data[0::3])
        greenImage = Image.new("L", (w, h))
        greenImage.fromstring(data[1::3])
        blueImage = Image.new("L", (w, h))
        blueImage.fromstring(data[2::3])
        
        if image.HasAlpha():
            alphaImage = Image.new("L", (w, h))
            alphaImage.fromstring(image.GetAlphaData())
            bands = (redImage, greenImage, blueImage, alphaImage)
            pil = Image.merge('RGBA', bands)
        else:
            pil = Image.merge('RGB', (redImage, greenImage, blueImage))
        return pil